package cz.muni.fi.pb138.webrep.repositories;

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */



import cz.muni.fi.pb138.webrep.util.Pair;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.basex.core.BaseXException;
import org.basex.core.Context;
import org.basex.core.cmd.Close;
import org.basex.core.cmd.CreateDB;
import org.basex.core.cmd.Open;
import org.basex.core.cmd.XQuery;

/**
 *
 * @author pinkdidi
 */
public class XsdRepository {
    private String xsdDBName;
    
    /**
     * Construct new instance of ArtifactRepository
     * 
     * @throws java.io.IOException
     * @throws org.basex.core.BaseXException
     */
    public XsdRepository() throws IOException {
        Properties p = new Properties();
        p.load(this.getClass().getResourceAsStream("../db_config.properties"));
        xsdDBName = p.getProperty("db.xsd.name");
        Context context = new Context();
        try {
            new Open(xsdDBName).execute(context);
        } catch(BaseXException e) {
            new CreateDB(xsdDBName, "<schemas/>").execute(context);
        } finally {
            new Close().execute(context);
        }
    }
    
    /**
     * Adds record with metadata of newly added version of an XSD schema artifact.
     * 
     * @param name name of artifact
     * @param version version of artifact
     * @param complexTypes array of names of complex types in added XSD schema
     * @param simpleTypes array of names of simple types in added XSD schema
     * @param elements array of names of elements in added XSD schema
     * @param attributes array of names of attributes in added XSD schema
     * @throws org.basex.core.BaseXException
     */
    public void addNewMetadata(String name, String version, 
            String complexTypes[], String simpleTypes[], String elements[], 
            String attributes[]) throws BaseXException {
        Context context = new Context();
        try {
            new Open(xsdDBName).execute(context);
            
            String query = "insert node <metadata name='"+ name +"' version='"+ version +"' />"
                    + " into /schemas";
            new XQuery(query).execute(context);
            
            addNewMetadataInsertEntries(name, version, 
                    complexTypes, "complexType", context);
            addNewMetadataInsertEntries(name, version, 
                    simpleTypes, "simpleType", context);
            addNewMetadataInsertEntries(name, version, 
                    elements, "element", context);
            addNewMetadataInsertEntries(name, version, 
                    attributes, "attribute", context);
        }
        finally {
            new Close().execute(context);
        }
    }
    private void addNewMetadataInsertEntries(String name, String version, 
            String entries[], String saveAs, Context context) throws BaseXException {
        for(String entry : entries) {
            String query = "insert node <"+ saveAs +">"+ entry +"</"+ saveAs +">"
                    + " into //metadata[@name='"+ name +"' "
                    + "     and @version='"+ version +"']";
            new XQuery(query).execute(context);
        }
    }
    
    /**
     * Returns array of metadata of given type for given version of artifact
     * with given name
     * 
     * @param type type of metadata, which is actually name of its element in DB
     * @param name name of artifact
     * @param version version of artifact
     * @return array of metadata of given type
     * @throws org.basex.core.BaseXException
     */
    public String[] getMetadata(String type, String name, String version) throws BaseXException{
        Context context = new Context();
        String[] result = {}; 
        try {
            new Open(xsdDBName).execute(context);
            String str = new XQuery("for $x in //metadata[@name='"+ name +"' and @version='"
                    + version +"']/"+ type
                    + " order by $x"
                    + " return concat(data($x), '/!@#%')").execute(context);
            int pos;
            if((pos = str.lastIndexOf("/!@#%")) >= 0) str = str.substring(0, pos);
            if(!str.equals("")) result = str.split("/!@#% ");
        } finally {
            new Close().execute(context);
        }
        return result;
    }
    
    /**
     * Finds versions of artifacts, which's metadata matches given restrictions.
     * 
     * @param restrictions map of restrictions, where key is metadata type
     *          and value is searched value
     * @return map, where keys are names of artifacts and values are sets of 
     *          numbers of matching versions
     * @throws org.basex.core.BaseXException
     */
    public Map<String, Set<Integer>> getArtifactVersionsByMetadata(List<Pair<String, String>> restrictions) 
            throws BaseXException {
        Map<String, Set<Integer>> result = new HashMap<>();
        Context context = new Context();
        try {
            new Open(xsdDBName).execute(context);
            
            Map<String, Set<Integer>> current = null;
            boolean firstRun = true;
            
            for(Pair<String,String> restr : restrictions) {
                current = new HashMap<>();
                String res = new XQuery("for $x in //metadata"
                        + " where $x/"+ restr.getKey() +" = '"+ restr.getValue() +"'"
                        + " order by $x/@name"
                        + " return concat(data($x/@name), '/!@#% ', data($x/@version), '/!@#%')").execute(context);
                
                int pos;
                if((pos = res.lastIndexOf("/!@#%")) >= 0) res = res.substring(0, pos);
                
                boolean nameNext = true;
                String currentName = "";
                for(String value : res.split("/!@#% ")) {
                    if(nameNext) {
                        currentName = value;
                        if(current.get(currentName) == null) current.put(currentName, new HashSet<Integer>());
                        nameNext = false;
                    }
                    else {
                        current.get(currentName).add(Integer.valueOf(value));
                        nameNext = true;
                    }
                }
                // intersect
                if(firstRun) result.putAll(current);
                else {
                    for(String key : result.keySet()) {
                        if(!current.containsKey(key)) result.remove(key);
                        else {
                            for(Integer value : result.get(key)) {
                                if(!current.get(key).contains(value)) result.get(key).remove(value);
                            }
                        }
                    }
                }
                firstRun = false;
            }
        } finally {
            new Close().execute(context);
        }
        return result;
    }
    
    /**
     * Deletes metadata associated with artifact with given name
     * 
     * @param artifactName name of the artifact 
     * @throws org.basex.core.BaseXException
     */
    public void deleteMetadata(String artifactName) throws BaseXException {
        Context context = new Context();
        try {
            new Open(xsdDBName).execute(context);
            
            new XQuery("delete nodes //metadata[@name = '"+ artifactName +"']").execute(context);
             
        } finally {
            new Close().execute(context);
        }
    }
}
